package com.small.nacos.naming.core;

import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.utils.NamingUtils;
import com.alibaba.nacos.common.utils.CollectionUtils;
import com.small.nacos.naming.consistency.ConsistencyService;
import com.small.nacos.naming.consistency.Datum;
import com.small.nacos.naming.consistency.KeyBuilder;
import com.small.nacos.naming.misc.UtilsAndCommons;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.CopyOnWriteArrayList;

import static com.small.nacos.naming.misc.UtilsAndCommons.UPDATE_INSTANCE_ACTION_ADD;


/**
 * @Author zhoujin
 * @Date 2022/7/22 20:53
 */
@Component
public class ServiceManager {

    Map<String, Map<String, Service>> serviceMap = new ConcurrentHashMap();

    private final Object LOCK = new Object();

    @Resource(name = "consistencyDelegate")
    private ConsistencyService consistencyService;

    public void registerInstance(String namespaceId, String serviceName, Instance instance) throws NacosException {
        //创建service
        createEmptyService(namespaceId, serviceName, instance.isEphemeral());

        //添加实例
        addInstance(namespaceId, serviceName, instance.isEphemeral(), instance);
    }

    private void addInstance(String namespaceId, String serviceName, boolean ephemeral, Instance... ips) throws NacosException {

        Service service = getService(namespaceId, serviceName);

        synchronized (service) {

            List<Instance> instanceList = updateIpAddresses(service, UPDATE_INSTANCE_ACTION_ADD, ephemeral, ips);

            Instances instances = new Instances();
            instances.setInstanceList(instanceList);
            String key = KeyBuilder.buildInstanceListKey(namespaceId, serviceName, ephemeral);
            consistencyService.put(key, instances);
        }
    }

    private List<Instance> updateIpAddresses(Service service, String action, boolean ephemeral, Instance... ips) throws NacosException {

        Datum<Instances> datum = consistencyService.get(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(), ephemeral));

        HashMap<String,Instance> currentInstances = new HashMap();
        for (Instance instance : ips) {
            currentInstances.put(instance.toIpAddr(), instance);
        }

        //一致性服务中所有实例
        Map<String, Instance> instanceMap;
        if (datum != null && null != datum.value) {
            //更新一致性服务下所有实例健康状态和最后心跳时间
            instanceMap= setValid(datum.value.getInstanceList(),currentInstances);
        }else {
            instanceMap = new HashMap<>();
        }

        for (Instance instance : ips) {
            if (!service.getClusterMap().containsKey(instance.getClusterName())) {
                Cluster cluster = new Cluster(instance.getClusterName(), service);
                service.getClusterMap().put(instance.getClusterName(), cluster);
            }

            if (UtilsAndCommons.UPDATE_INSTANCE_ACTION_REMOVE.equals(action)){
                instanceMap.remove(instance.getDatumKey());
            }else {
                Instance old = instanceMap.get(instance.getDatumKey());
                if (old != null){
                    instance.setInstanceId(old.getInstanceId());
                }else {
                    instance.setInstanceId(instance.generateInstanceId());
                }
                instanceMap.put(instance.getDatumKey(),instance);
            }

        }
        return new CopyOnWriteArrayList(instanceMap.values());
    }

    private Map<String, Instance> setValid(List<Instance> oldInstances, Map<String, Instance> map) {
        HashMap<String,Instance> instanceMap = new HashMap();
        for (Instance oldInstance : oldInstances) {
            if (map.containsKey(oldInstance.toIpAddr())) {
                oldInstance.setLastBeat(map.get(oldInstance.toIpAddr()).getLastBeat());
                oldInstance.setHealthy(map.get(oldInstance.toIpAddr()).isHealthy());
            }
            instanceMap.put(oldInstance.getDatumKey(), oldInstance);
        }
        return instanceMap;
    }


    public void createEmptyService(String namespaceId, String serviceName, boolean local) throws NacosException {
        createServiceIfAbsent(namespaceId, serviceName, local, null);
    }

    public void createServiceIfAbsent(String namespaceId, String serviceName, boolean local, Cluster cluster)
            throws NacosException {

        if (getService(namespaceId, serviceName) != null) {
            return;
        }
        Service service = new Service();
        service.setName(serviceName);
        service.setNamespaceId(namespaceId);
        service.setGroupName(NamingUtils.getGroupName(serviceName));
        service.setLastModifiedMillis(System.currentTimeMillis());
        if (cluster != null) {
            cluster.setService(service);
            service.getClusterMap().put(cluster.getName(), cluster);
        }
        putServiceAndInit(service);

        //TODO 非临时节点持久化

    }

    private void putServiceAndInit(Service service) throws NacosException {
        putService(service);
        service = getService(service.getNamespaceId(), service.getName());
        service.init();

        consistencyService.listen(KeyBuilder.buildInstanceListKey(service.getNamespaceId(), service.getName(),true),service);
    }

    public void putService(Service service) {
        if (!serviceMap.containsKey(service.getNamespaceId())) {
            synchronized (LOCK) {
                if (!serviceMap.containsKey(service.getNamespaceId())) {
                    serviceMap.put(service.getNamespaceId(), new ConcurrentSkipListMap<>());
                }
            }
        }
        serviceMap.get(service.getNamespaceId()).putIfAbsent(service.getName(), service);
    }

    public Service getService(String namespaceId, String serviceName) {
        if (chooseServiceMap(namespaceId) != null) {
            return chooseServiceMap(namespaceId).get(serviceName);
        }
        return null;
    }

    public Map<String, Service> chooseServiceMap(String namespaceId) {
        return serviceMap.get(namespaceId);
    }

    public void removeInstance(String namespaceId, String serviceName, boolean ephemeral, Instance ips) throws NacosException {

        Service service = getService(namespaceId, serviceName);

        synchronized (service) {
            substractIpAddresses(service, ephemeral, ips);
        }
    }

    private void substractIpAddresses(Service service, boolean ephemeral, Instance... ips)
            throws NacosException {
        updateIpAddresses(service, UtilsAndCommons.UPDATE_INSTANCE_ACTION_REMOVE, ephemeral, ips);
    }

    public Instance getInstance(String namespaceId, String serviceName, String cluster, String ip, int port, Boolean ephemeral) {
        Service service = getService(namespaceId, serviceName);
        if (service == null) {
            return null;
        }

        List<Instance> ips = service.allIPs(Arrays.asList(cluster), ephemeral);
        if (ips == null || ips.isEmpty()) {
            return null;
        }

        for (Instance instance : ips) {
            if (instance.getIp().equals(ip) && instance.getPort() == port) {
                return instance;
            }
        }

        return null;
    }
}
